Skip to main content

Fetching Data

Learning Objectives
  • Use `requests` to fetch data from the internet using Python
  • Parse JSON into a Python dictionary
  • Extract data from a Python dictionary

Pre-work​

No Pre-work for Afternoon Workshops

Workshop​

Recap the ideas that we spoke about in the morning session.

  • Clients are the users of a service (i.e. a laptop)
  • Servers are the providers of a service (i.e. a website on the internet)
  • APIs are the way that clients and servers communicate
  • HTTP is the protocol that APIs use to communicate

Introduce the requests library, which is a simple way to make HTTP requests in Python.

Explain what JSON is and the difference between JSON strings and Python dictionaries:

  • JSON (JavaScript Object Notation) is a standard text-based format for representing structured data based on JavaScript object syntax
  • JSON is commonly used for transmitting data in web applications between a server and client
  • When we make API requests, the response is often in JSON format (a string)
  • The response.json() method converts the JSON string into a Python dictionary
  • A JSON string looks like '{"name": "John", "age": 30}' (note the quotes and double quotes)
  • After conversion, it becomes a Python dictionary: {"name": "John", "age": 30} (no outer quotes)
  • This conversion is essential because we can't directly access data in a JSON string, but we can easily access data in a Python dictionary using keys

Live code Using the GitHub API from the morning workshop create a user searcher that will

  • Accept a search from the user of a GitHub username
  • List the user's
    • name
    • company
    • number of public repositories
  • Show an error message if the username cannot be found
  • Handle any missing data gracefully

Introduce the requests-mock library, which allows us to mock the requests library in our tests.

Create some example tests, showing how to mock the requests library and handle raised exceptions.

Point out that -- though mocking makes tests more complex -- these tests still follow the AAA framework. As we have more complex code now, we need a more complex "Arrange" step.

Stress the importance of consistent testing to trainees.

Give the trainees the task of completing the following task in pairs

Adding another request that fetches the user's first five repositories displaying the name and stars number for each.

View the solution to the whole challenge can be found below:

import requests


def load_user_data(user_name: str) -> dict:
"""Searches for a username on the GitHub API, returning the response data."""
response = requests.get(f"https://api.github.com/users/{user_name}")
if response.status_code == 200:
return response.json()
else:
raise requests.exceptions.HTTPError("Unable to retrieve user data.")


def display_user_data(data: dict):
"""Prints GitHub user information from a dict."""
print(f"User: {data['name']} ({data['login']})")
if "company" in data:
print(f"Company: {data['company']}")
print(f"Public repositories: {data['public_repos']}")


def load_user_repository_data(user_name: str) -> list:
"""Returns data on 5 of a GitHub user's repositories."""
response = requests.get(f"https://api.github.com/users/{user_name}/repos?per_page=5")
if response.status_code == 200:
return response.json()
else:
raise requests.exceptions.HTTPError("Unable to retrieve repository data.")


def display_repository_data(data: list):
"""Prints GitHub repository information from a list of dicts."""
print("First five repositories:")
for repo in data:
print(f" - {repo['name']} ({repo['stargazers_count']} stars)")


def main():
"""Prompts the user for a GitHub username and displays details about the matching account."""
try:
user_name = input("Enter a GitHub username: ")

if len(user_name) == 0:
raise ValueError("Cannot search for an empty username.")

user_data = load_user_data(user_name)
display_user_data(user_data)

repos = load_user_repository_data(user_name)
display_repository_data(repos)

except Exception as e:
print(f"Error: {e.args[0]}")


if __name__ == "__main__":
main()